/*
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.chimos.property.compiler;

import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;

import java.util.Locale;

/**
 * @author Niklas Hofmann
 */
public class PropertyInfo {
    
    public final String fullPropertyName;
    
    public final String propertyName;
    public final String propertyNameCapital;
    
    public final String humanReadablePropertyName;
    
    public final String className;
    
    public final int readAccessLevel;
    public final int writeAccessLevel;
    
    public final String getterMethodName;
    public final String setterMethodName;
    
    public final String internalGetterMethodName;
    public final String internalSetterMethodName;
    
    public final String propertyFieldName;
    public final String propertyMethodName;
    public final String readOnlyPropertyMethodName;
    
    public final String dataType;
    public final String dataSignature;
    
    public final String propertyDataType;
    public final String propertyDataSignature;
    
    public final String propertyInterfaceType;
    public final String propertyInterfaceSignature;
    
    public final String readOnlyPropertyInterfaceType;
    public final String readOnlyPropertyInterfaceSignature;
    
    public final String dataTypeGeneric;
    public final String dataSignatureGeneric;
    
    public final String propertyDataTypeGeneric;
    public final String propertyDataSignatureGeneric;
    
    public final String propertyInterfaceTypeGeneric;
    public final String propertyInterfaceSignatureGeneric;
    
    public final String readOnlyPropertyInterfaceTypeGeneric;
    public final String readOnlyPropertyInterfaceSignatureGeneric;
    
    public final boolean enableSerialization;
    
    public final boolean isCompiled;
    
    protected final String STRING_DATA_TYPE = "java/lang/String";
    protected final String BOOLEAN_DATA_TYPE = "java/lang/Boolean";
    protected final String FLOAT_DATA_TYPE = "java/lang/Float";
    protected final String DOUBLE_DATA_TYPE = "java/lang/Double";
    protected final String INTEGER_DATA_TYPE = "java/lang/Integer";
    protected final String LONG_DATA_TYPE = "java/lang/Long";
    
    public PropertyInfo(String className, String propertyName, String humanReadablePropertyName, String dataSignature, String dataSignatureGeneric, int readAccessLevel, int writeAccessLevel, boolean enableSerialization, boolean isCompiled) throws Exception
    {
        // Check access level order
        if((writeAccessLevel == ACC_PROTECTED && readAccessLevel == ACC_PRIVATE)
           || (writeAccessLevel == ACC_PUBLIC && (readAccessLevel == ACC_PROTECTED ||readAccessLevel == ACC_PRIVATE)))
        {
            throw new Exception("Read access level is lower than write access level");
        }
        
        if(enableSerialization == true && (writeAccessLevel != ACC_PUBLIC || readAccessLevel != ACC_PUBLIC))
        {
            throw new Exception("Public read and write access to property required to enable serialization");
        }
        
        // Provided attributes
        this.className = className;
        this.propertyName = propertyName;
        this.humanReadablePropertyName = humanReadablePropertyName;
        this.dataSignature = dataSignature;
        this.dataSignatureGeneric = dataSignatureGeneric;
        this.readAccessLevel = readAccessLevel;
        this.writeAccessLevel = writeAccessLevel;
        this.enableSerialization = enableSerialization;
        this.isCompiled = isCompiled;
    
        // Derive full property name
        this.fullPropertyName = this.className + "/" + this.propertyName;
        
        // Derive capital property name
        this.propertyNameCapital = this.propertyName.substring(0, 1).toUpperCase(Locale.ROOT) + this.propertyName.substring(1);
        
        // Derive getter and setter methods
        this.getterMethodName = "get" + this.propertyNameCapital;
        this.setterMethodName = "set" + this.propertyNameCapital;
        
        this.internalGetterMethodName = "_get" + this.propertyNameCapital;
        this.internalSetterMethodName = "_set" + this.propertyNameCapital;
        
        // Derive property field
        this.propertyFieldName = "_" + this.propertyName + "Field";
        
        // Derive property method name
        if(readAccessLevel != writeAccessLevel)
        {
            this.propertyMethodName = "_" + this.propertyName + "Property";
            this.readOnlyPropertyMethodName = this.propertyName + "Property";
        }
        else
        {
            this.propertyMethodName = this.propertyName + "Property";
            this.readOnlyPropertyMethodName = null;
        }
        
        // Derive data type
        this.dataType = this.dataSignature.substring(1, this.dataSignature.length()-1);
        
        // Derive property data type and signature
        if(readAccessLevel != writeAccessLevel)
        {
            switch(this.dataType)
            {
                case STRING_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyStringWrapper";
                    break;
                case BOOLEAN_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyBooleanWrapper";
                    break;
                case FLOAT_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyFloatWrapper";
                    break;
                case DOUBLE_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyDoubleWrapper";
                    break;
                case INTEGER_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyIntegerWrapper";
                    break;
                case LONG_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyLongWrapper";
                    break;
                default:
                    this.propertyDataType = "javafx/beans/property/ReadOnlyObjectWrapper";
                    break;
            }
        }
        else
        {
            switch(this.dataType)
            {
                case STRING_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/SimpleStringProperty";
                    break;
                case BOOLEAN_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/SimpleBooleanProperty";
                    break;
                case FLOAT_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/SimpleFloatProperty";
                    break;
                case DOUBLE_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/SimpleDoubleProperty";
                    break;
                case INTEGER_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/SimpleIntegerProperty";
                    break;
                case LONG_DATA_TYPE:
                    this.propertyDataType = "javafx/beans/property/SimpleLongProperty";
                    break;
                default:
                    this.propertyDataType = "javafx/beans/property/SimpleObjectProperty";
                    break;
            }
        }
        
        this.propertyDataSignature = "L"+this.propertyDataType+";";
        
        // Derive property interface type and signature
        
        switch(this.dataType)
        {
            case STRING_DATA_TYPE:
                this.propertyInterfaceType = "javafx/beans/property/StringProperty";
                break;
            case BOOLEAN_DATA_TYPE:
                this.propertyInterfaceType = "javafx/beans/property/BooleanProperty";
                break;
            case FLOAT_DATA_TYPE:
                this.propertyInterfaceType = "javafx/beans/property/FloatProperty";
                break;
            case DOUBLE_DATA_TYPE:
                this.propertyInterfaceType = "javafx/beans/property/DoubleProperty";
                break;
            case INTEGER_DATA_TYPE:
                this.propertyInterfaceType = "javafx/beans/property/IntegerProperty";
                break;
            case LONG_DATA_TYPE:
                this.propertyInterfaceType = "javafx/beans/property/LongProperty";
                break;
            default:
                this.propertyInterfaceType = "javafx/beans/property/ObjectProperty";
                break;
        }
        
        this.propertyInterfaceSignature = "L"+this.propertyInterfaceType+";";
        
        if(readAccessLevel != writeAccessLevel)
        {
            switch(this.dataType)
            {
                case STRING_DATA_TYPE:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyStringProperty";
                    break;
                case BOOLEAN_DATA_TYPE:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyBooleanProperty";
                    break;
                case FLOAT_DATA_TYPE:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyFloatProperty";
                    break;
                case DOUBLE_DATA_TYPE:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyDoubleProperty";
                    break;
                case INTEGER_DATA_TYPE:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyIntegerProperty";
                    break;
                case LONG_DATA_TYPE:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyLongProperty";
                    break;
                default:
                    this.readOnlyPropertyInterfaceType = "javafx/beans/property/ReadOnlyObjectProperty";
                    break;
            }

            this.readOnlyPropertyInterfaceSignature = "L"+this.readOnlyPropertyInterfaceType+";";
        }
        else
        {
            this.readOnlyPropertyInterfaceType = null;
            this.readOnlyPropertyInterfaceSignature = null;
        }
        
        // Derive generic variant of data signature
        if(this.dataSignatureGeneric != null)
        {
            this.dataTypeGeneric = this.dataSignatureGeneric.substring(1, this.dataSignatureGeneric.length()-1).replaceAll("<L", "<").replaceAll(";>", ">");
        }
        else
        {
            this.dataTypeGeneric = null;
        }
        
        // Derive generic variant of property data type and signature
        switch(this.dataType)
        {
            case STRING_DATA_TYPE:
            case BOOLEAN_DATA_TYPE:
            case FLOAT_DATA_TYPE:
            case DOUBLE_DATA_TYPE:
            case INTEGER_DATA_TYPE:
            case LONG_DATA_TYPE:
                this.propertyDataTypeGeneric = null;
                this.propertyDataSignatureGeneric = null;
                break;
                
            default:
                this.propertyDataTypeGeneric = this.propertyDataType+"<"+(this.dataTypeGeneric!=null?this.dataTypeGeneric:this.dataType)+">";
                this.propertyDataSignatureGeneric = "L"+this.propertyDataType+"<"+(this.dataSignatureGeneric!=null?this.dataSignatureGeneric:this.dataSignature)+">;";
                break;
        }
        
        // Derive generic variant of property interface type and signature
        
        switch(this.dataType)
        {
            case STRING_DATA_TYPE:
            case BOOLEAN_DATA_TYPE:
            case FLOAT_DATA_TYPE:
            case DOUBLE_DATA_TYPE:
            case INTEGER_DATA_TYPE:
            case LONG_DATA_TYPE:
                
                this.propertyInterfaceTypeGeneric = null;
                this.propertyInterfaceSignatureGeneric = null;

                this.readOnlyPropertyInterfaceTypeGeneric = null;
                this.readOnlyPropertyInterfaceSignatureGeneric = null;
                
                break;
                
            default:
                
                this.propertyInterfaceTypeGeneric = this.propertyInterfaceType+"<"+(this.dataTypeGeneric!=null?this.dataTypeGeneric:this.dataType)+">";
                this.propertyInterfaceSignatureGeneric = "L"+this.propertyInterfaceType+"<"+(this.dataSignatureGeneric!=null?this.dataSignatureGeneric:this.dataSignature)+">;";

                if(readAccessLevel != writeAccessLevel)
                {
                    this.readOnlyPropertyInterfaceTypeGeneric = this.readOnlyPropertyInterfaceType+"<"+(this.dataTypeGeneric!=null?this.dataTypeGeneric:this.dataType)+">";
                    this.readOnlyPropertyInterfaceSignatureGeneric = "L"+this.readOnlyPropertyInterfaceType+"<"+(this.dataSignatureGeneric!=null?this.dataSignatureGeneric:this.dataSignature)+">;";
                }
                else
                {
                    this.readOnlyPropertyInterfaceTypeGeneric = null;
                    this.readOnlyPropertyInterfaceSignatureGeneric = null;
                }
                
                break;
        }
        
    }
    
}
